<template>
  <Uploader
    ref="uploaderInst"
    :options="options"
    :autoStart="true"
    :file-status-text="fileStatusText"
    @file-added="onFileAdded"
    @file-success="onFileSuccess"
    @file-progress="onFileProgress"
    @file-error="onFileError"
    @fileRemoved="onFileRemoved"
  >
    <UploaderUnsupport />
    <UploaderBtn :attrs="attrs">{{ triggerText }}</UploaderBtn>
    <UploaderFiles ref="uploaderFilesInst">
      <template v-slot="{ files }">
        <ul
          v-if="defaultFileList?.length"
          class="vu-files"
        >
          <li
            v-for="(file, i) in defaultFileList"
            :key="i"
            class="vu-files__item"
            :class="{
              error: file.error,
              completed: file.completed
            }"
          >
            <span>{{ file.name }}</span>
            <span>100%</span>
            <span
              v-if="!disabled"
              class="file-delete"
              @click="deleteFile(file)"
            >
              <NIcon size="23">
                <Delete48Regular />
              </NIcon>
            </span>
          </li>
        </ul>
        <ul class="vu-files">
          <li
            v-for="(file, i) in files"
            :key="i"
            :class="{
              error: file.error,
              completed: file.completed
            }"
          >
            <div class="vu-files__item">
              <div class="link-icon">
                <svg
                  viewBox="0 0 16 16"
                  version="1.1"
                  xmlns="http://www.w3.org/2000/svg"
                >
                  <g
                    stroke="none"
                    stroke-width="1"
                    fill="none"
                    fill-rule="evenodd"
                  >
                    <g
                      fill="currentColor"
                      fill-rule="nonzero"
                    >
                      <path
                        d="M3.25735931,8.70710678 L7.85355339,4.1109127 C8.82986412,3.13460197 10.4127766,3.13460197 11.3890873,4.1109127 C12.365398,5.08722343 12.365398,6.67013588 11.3890873,7.64644661 L6.08578644,12.9497475 C5.69526215,13.3402718 5.06209717,13.3402718 4.67157288,12.9497475 C4.28104858,12.5592232 4.28104858,11.9260582 4.67157288,11.5355339 L9.97487373,6.23223305 C10.1701359,6.0369709 10.1701359,5.72038841 9.97487373,5.52512627 C9.77961159,5.32986412 9.4630291,5.32986412 9.26776695,5.52512627 L3.96446609,10.8284271 C3.18341751,11.6094757 3.18341751,12.8758057 3.96446609,13.6568542 C4.74551468,14.4379028 6.01184464,14.4379028 6.79289322,13.6568542 L12.0961941,8.35355339 C13.4630291,6.98671837 13.4630291,4.77064094 12.0961941,3.40380592 C10.7293591,2.0369709 8.51328163,2.0369709 7.14644661,3.40380592 L2.55025253,8 C2.35499039,8.19526215 2.35499039,8.51184464 2.55025253,8.70710678 C2.74551468,8.90236893 3.06209717,8.90236893 3.25735931,8.70710678 Z"
                      ></path>
                    </g>
                  </g>
                </svg>
              </div>
              <span class="file-name">{{ file.name }}</span>
              <span>{{ (file.progress() * 100).toFixed(1) }}%</span>
            </div>
            <span
              v-if="!disabled"
              class="file-delete"
              @click="deleteFile(file)"
            >
              <NIcon size="23">
                <Delete48Regular />
              </NIcon>
            </span>
          </li>
        </ul>
      </template>
    </UploaderFiles>
  </Uploader>
</template>

<script setup>
import { Uploader, UploaderUnsupport, UploaderBtn, UploaderFiles } from './core'
import SparkMD5 from 'spark-md5'
import { NIcon, useMessage } from 'naive-ui'
import { sendRequest } from '@/utils/api'
import { Delete48Regular } from '@vicons/fluent'
import { nextTick, ref } from 'vue'

const props = defineProps({
  triggerText: {
    type: String,
    default: '点击上传'
  },
  defaultFileList: Array,
  fileList: Array,
  disabled: Boolean,
  accept: String
})

const emit = defineEmits(['update:file-list', 'update:default-file-list', 'remove', 'finish'])

const message = useMessage()
const uploaderInst = ref(null)

const ACCEPT_CONFIG = {
  image: ['.png', '.jpg', '.jpeg', '.gif', '.bmp'],
  video: ['.mp4', '.rmvb', '.mkv', '.wmv', '.flv'],
  document: [
    '.doc',
    '.docx',
    '.xls',
    '.xlsx',
    '.ppt',
    '.pptx',
    '.pdf',
    '.txt',
    '.tif',
    '.tiff',
    '.rar',
    '.zip'
  ],
  get all() {
    return [...this.image, ...this.video, ...this.document]
  }
}

const options = {
  //目标上传 URL，默认POST
  target: import.meta.env.VITE_API_BASE_URL + '/common/bigFile/uploader/chunk',
  //分块大小(单位：字节)
  chunkSize: '2048000',
  //上传文件时文件内容的参数名，对应chunk里的Multipart对象名，默认对象名为file
  fileParameterName: 'upfile',
  //失败后最多自动重试上传次数
  maxChunkRetries: 3,
  //是否开启服务器分片校验，对应GET类型同名的target URL
  testChunks: true,
  /*
                    服务器分片校验函数，判断秒传及断点续传,传入的参数是Uploader.Chunk实例以及请求响应信息
                    reponse码是successStatuses码时，才会进入该方法
                    reponse码如果返回的是permanentErrors 中的状态码，不会进入该方法，直接进入onFileError函数 ，并显示上传失败
                    reponse码是其他状态码，不会进入该方法，正常走标准上传
                    checkChunkUploadedByResponse函数直接return true的话，不再调用上传接口
                    */
  checkChunkUploadedByResponse: function (chunk, response_msg) {
    let objMessage = JSON.parse(response_msg)
    if (objMessage.skipUpload) {
      return true
    }
    return (objMessage.uploadedChunks || []).indexOf(chunk.offset + 1) >= 0
  }
}

const attrs = {
  accept: props.accept ? ACCEPT_CONFIG[props.accept] : ACCEPT_CONFIG.all
}

const fileStatusText = {
  success: '上传成功',
  error: '上传失败',
  uploading: '上传中',
  paused: '暂停',
  waiting: '等待上传'
}

const getFileList = () => {
  if (!uploaderInst.value) return
  return uploaderInst.value.files.filter((e) => e.completed)
}

const deleteFile = (file) => {
  console.log(uploaderInst.value)
  console.log(file)
  if (!uploaderInst.value) return
  emit('remove', file.remoteId)
  if (file.remote) {
    emit(
      'update:default-file-list',
      props.defaultFileList.filter((e) => e.id !== file.id)
    )
    return
  }
  uploaderInst.value.uploader?.removeFile(file)
}

const onFileRemoved = () => {
  emit('update:file-list', getFileList())
}

function onFileError(rootFile, file, response, chunk) {
  // console.log('上传完成后异常信息：' + response)
}
function onFileProgress(rootFile, file, chunk) {
  nextTick(() => {
    uploaderFilesInst.value?.$forceUpdate()
  })
}
function onFileAdded(file) {
  computeMD5(file)
}

const clearFiles = () => {
  if (!uploaderInst.value) return
  uploaderInst.value.files.forEach(f => {
    uploaderInst.value.uploader?.removeFile(f)
  })
}

defineExpose({
  clearFiles
});

const uploaderFilesInst = ref(null)
/*
            第一个参数 rootFile 就是成功上传的文件所属的根 Uploader.File 对象，它应该包含或者等于成功上传文件；
            第二个参数 file 就是当前成功的 Uploader.File 对象本身；
            第三个参数就是 message 就是服务端响应内容，永远都是字符串；
            第四个参数 chunk 就是 Uploader.Chunk 实例，它就是该文件的最后一个块实例，如果你想得到请求响应码的话，chunk.xhr.status就是
            */
function onFileSuccess(rootFile, file, response, chunk) {
  file.refProjectId = '123456789'
  sendRequest({
    module: 'upload',
    api: 'mergeFile',
    override: {
      data: file
    }
  })
    .then(([err, res]) => {
      if (err) throw err
      if (res.code !== 0) throw new Error(res.msg)
      file.id = res.data.fileId
      file.url = res.data.url
      file.remoteId = res.data.fileId
      file.file = file
      emit('finish', file)
      emit('update:file-list', getFileList())

      nextTick(() => {
        uploaderFilesInst.value?.$forceUpdate()
      })
      message.success(res.msg)
    })
    .catch(function (error) {
      message.error(error.message)
    })
}

/**
 * 计算md5，实现断点续传及秒传
 * @param file
 */
function computeMD5(file) {
  file.pause()

  //单个文件的大小限制2G
  let fileSizeLimit = 200 * 1024 * 1024 * 1024
  // console.log('文件大小：' + file.size)
  // console.log('限制大小：' + fileSizeLimit)
  if (file.size > fileSizeLimit) {
    message.warning({
      content: '文件大小不能超过2G'
    })
    file.cancel()
  }

  let fileReader = new FileReader()
  let time = new Date().getTime()
  let blobSlice = File.prototype.slice || File.prototype.mozSlice || File.prototype.webkitSlice
  let currentChunk = 0
  const chunkSize = 10 * 1024 * 1000
  let chunks = Math.ceil(file.size / chunkSize)
  let spark = new SparkMD5.ArrayBuffer()
  //由于计算整个文件的Md5太慢，因此采用只计算第1块文件的md5的方式
  let chunkNumberMD5 = 1

  loadNext()

  fileReader.onload = (e) => {
    spark.append(e.target.result)

    if (currentChunk < chunkNumberMD5) {
      loadNext()
    } else {
      let md5 = spark.end()
      file.uniqueIdentifier = md5
      file.resume()
      // console.log(
      //   `MD5计算完毕：${file.name} \nMD5：${md5} \n分片：${chunks} 大小:${file.size} 用时：${
      //     new Date().getTime() - time
      //   } ms`
      // )
    }
  }

  fileReader.onerror = function () {
    message.error({
      content: `文件${file.name}读取出错，请检查该文件`
    })
    file.cancel()
  }

  function loadNext() {
    let start = currentChunk * chunkSize
    let end = start + chunkSize >= file.size ? file.size : start + chunkSize

    fileReader.readAsArrayBuffer(blobSlice.call(file.file, start, end))
    currentChunk++
    // console.log('计算第' + currentChunk + '块')
  }
}
</script>

<style scoped lang="scss">
.uploader-drop {
  border: none;
  padding: 0;
  background-color: transparent;
}

.uploader-btn {
  border: 1px solid #5fb17f;
  color: #5fb17f;
}

.vu-files {
  display: flex;
  flex-direction: column;
  gap: 3rem 0;
  list-style: none;
  padding: 5rem 0 0;

  li {
    display: flex;
    align-items: center;
    justify-content: space-between;
    // background-color: #f5f7fa;
    padding-right: 0.5em;

    &.error:hover {
      .vu-files__item {
        background-color: rgba(red, 0.15);
      }
    }

    &.completed:hover {
      .vu-files__item {
        background-color: rgba(#5fb17f, 0.15);
      }
    }
  }

  .vu-files__item {
    display: flex;
    align-items: center;
    padding: 10rem 10rem 10rem 10rem;
    border-radius: 3px;
    margin-right: 0.5em;
    overflow: hidden;
    transition: all 0.2s ease-in-out;
    cursor: pointer;

    .link-icon {
      display: flex;
      width: 16px;
      height: 16px;
      fill: currentColor;
      color: #5fb17f;
      margin-right: 0.5em;
    }

    .file-name {
      margin-right: 1em;
    }
  }

  li:hover > .file-delete {
    display: flex;
  }

  .file-delete {
    display: none;
    // display: flex;
    align-items: center;
    cursor: pointer;
    transition: all 0.2s ease-in-out;
    border-radius: 2px;

    &:hover {
      background-color: rgba(0, 0, 0, 0.15);
    }
  }
}
</style>
